<?php
/**
 * [ActiveRecord] 模型数据操作或查询条件类,构造出查询SQL
 *
 * @version $Id: Condition.php 134 2013-09-25 07:54:50Z linsir123 $
 * @package ActiveRecord
 */
class ARCondition extends SqlBase
{
	/**
	 * 数据表替换
	 */
	const AS_TABLE = '`a`';

	/**
	 * 数据表别名
	 *
	 * @var string
	 */
	public $asTable;

	/**
	 * 条件数组
	 *
	 * @var array
	 */
	public $data;

	/**
	 * `数据库表名`
	 *
	 * @var string
	 */
	public $table;

	/**
	 * 是否连接过
	 *
	 * @var bool
	 */
	private $_hadConn;

	/**
	 * 构造函数
	 *
	 * @param string $table 需要查询的数据库表名
	 */
	public function __construct($table)
	{
		$this->setTable($table);

		///
		$this->clean();
	}

	/**
	 * 输出对象对应的SQL语句
	 */
	public function __toString()
	{
		if (empty($this->data))
			return '';

		///
		$list = array();
		foreach ($this->data as $v) {
			$func = $v['type'] == 1 ? '_fromArray' : '_fromString';
			$params = array(
				'data' => $v['data'],
				'and' => $v['and'],
				'as_table' => $v['as_table']
			);
			$tmp = call_user_func_array(array($this, $func), $params);
			if (! empty($tmp))
				$list[] = $tmp;
		}

		///
		return empty($list) ? '' : ' WHERE ' . implode(' ', $list);
	}

	/**
	 * 添加条件
	 *
	 * @param mixed $data 条件值数组(若采用数组方式则是一个二维数组)
	 * @param bool $and 条件连接关系(true:`AND`, false:`OR`)
	 */
	public function add($data, $and = true)
	{
		if (count($data) == 1 && is_array($data[0])) {
			$data = array_shift($data);
			$type = 1;
		}
		else
			$type = 0;

		///
		$this->data[] = array(
			'type' => $type,
			'data' => $data,
			'and' => $and,
			'as_table' => null
		);
	}

	/**
	 * 清空条件内容
	 */
	public function clean()
	{
		$this->data = array();
		$this->asTable = null;
		$this->_hadConn = false;
	}

	/**
	 * 重新设置操作的数据表名
	 *
	 * @param string $table 需要查询的数据库表名
	 */
	public function setTable($table)
	{
		$this->table = $this->_quote($table);
	}

	/**
	 * 表连接时,将数据进行合并
	 *
	 * @param ARCondition $oCodition 被连接的对象
	 * @param string $asTable AS表名
	 */
	public function join($oCodition, $asTable)
	{
		foreach ($oCodition->data as $v) {
			if (empty($v['as_table']))
				$v['as_table'] = $asTable;

			///
			$this->data[] = $v;
		}
	}

	/**
	 * 取得默认的数据表别名
	 */
	private function _getAsTable($default = null)
	{
		if (! empty($default))
			return $default;

		///
		return empty($this->asTable) ? self::AS_TABLE : $this->asTable;
	}

	/**
	 * 字符串条件,解析方法
	 * // 使用(?)来替换
	 *
	 * @example where('id=1')
	 * @example where('id', 1)
	 * @example where('id=?', 1)
	 * @example where('id in(?)', '1,2,3')
	 * @example where('title like "%?%"', 'linxs')
	 */
	private function _fromString($data, $and, $asTable = null)
	{
		$asTable = $this->_getAsTable($asTable);

		///
		$pIn = "/IN\s?\($/i";
		$pLike = "/LIKE [\'|\"](\%)?$/i";
		$format = explode('?', $data[0]);
		$sql = '';
		$i = 0;

		///
		if (count($format) == 1 && count($data) == 2) {
			$sql = $this->_toSql($data[0], $data[1], $asTable);

			return $this->_conn($and, $sql);
		}

		///
		array_shift($data);
		if (count($format) - 1 == count($data)) {
			foreach ($format as $tmp) {
				if (isset($data[$i])) {
					$var = $data[$i];
					if (preg_match($pLike, $tmp) || preg_match($pIn, $tmp))
						$tmp .= $var;
					else
						$tmp .= $this->_escape($var);
				}

				///
				$sql .= $tmp;
				$i ++;
			}
			if (! empty($sql))
				return $this->_conn($and, $sql);
		}

		///
		return '';
	}

	/**
	 * 数组条件,解析方法
	 *
	 * @example where(array('id'=>2,'uid'=>1))
	 * @example where(array('id'=>array('in', array(1,2,3))))
	 * @example where(array('title'=>array('like', '"%keyword%"')))
	 * @example where(array('title'=>array('like', '"%?%"', $keyword)))
	 */
	private function _fromArray($data, $and, $asTable = null)
	{
		$asTable = $this->_getAsTable($asTable);

		///
		$i = 0;
		$sql = '';
		$count = count($data);
		foreach ($data as $field => $var) {
			if ($i > 0 && $i < $count)
				$sql .= ' AND ';

			///
			$i ++;
			$sql .= $this->_toSql($field, $var, $asTable);
		}

		///
		if (! empty($sql))
			return $this->_conn($and, $sql);

		///
		return '';
	}

	/**
	 * 格式化
	 * // 格式：key => val
	 *
	 * @example where('id', 2)
	 * @example where('id', array('in', array(1,2,3)))
	 * @example where('title', array('like', '"%keyword%"'))
	 * @example where('title', array('like', '"%?%"', $keyword))
	 */
	private function _toSql($field, $var, $asTable)
	{
		$sql = '';

		///
		$field = $asTable . '.' . $this->_quote($field);

		///
		if (is_array($var) && count($var) >= 2) {
			$type = array_shift($var);
			$type = strtoupper($type);
			switch ($type) {
				case 'IN':
				case 'NOT IN':
					if (is_array($var[0]) && ! empty($var[0])) {
						$data = $this->_escape($var[0]);
						$data = implode(',', $data);

						///
						$sql .= $field . " {$type}(" . $data . ')';
					}
					break;

				case 'BETWEEN':
				case 'NOT BETWEEN':
					if (count($var) == 2) {
						$start = $this->_escape($var[0]);
						$end = $this->_escape($var[1]);

						///
						$sql .= $field . " {$type} " . $start . " AND " . $end;
					}
					break;

				case 'LIKE':
				case 'NOT LIKE':
					if (count($var) == 2) {
						$keyword = addcslashes($var[1], '%_"\'');
						$data = str_replace('?', $keyword, $var[0]);
					}
					else
						$data = $this->_escape($var[0]);

					///
					$sql .= $field . " {$type} " . $data;
					break;

				default:
					$data = $this->_escape($var[0]);

					///
					$sql .= $field . " {$type} " . $data;
					break;
			}
		}
		else
			$sql .= $field . ' = ' . $this->_escape($var);

		///
		return $sql;
	}

	/**
	 *
	 * 连接SQL
	 *
	 * @param bool $and
	 * @param string $sql
	 *
	 * @return string
	 */
	private function _conn($and, $sql)
	{
		if (! $this->_hadConn) {
			$this->_hadConn = true;
			return $sql;
		}

		return sprintf("%s %s", $and ? 'AND' : 'OR', $sql);
	}
}
